#include "panda_resolver_impl.hpp"

#include "panda_log.hpp"
#include "panda_thread_impl.hpp"

#include <cstring>
#include <memory>

PandaImpl::ResolverImpl::ResolverImpl(ThreadImpl* thread) : thread_(thread) {}

bool PandaImpl::ResolverImpl::SyncResolver(const char* url,
                                           Panda::OnResolve on_resolver) {
  return this->thread_->Invoke<bool>(PandaFromHere, [&]() {
    this->resolver_info.emplace_back(ResolverContext());
    ResolverContext& context = this->resolver_info.back();
    context.req.data = this;
    context.url = url;
    context.on_resolver = std::move(on_resolver);
    int r =
        uv_getaddrinfo(this->thread_->GetLoop(), &context.req,
                       &UVHelper::UVResolverCb, url, NULL, &context.addr_info);
    if (r) {
      LOG(Error) << "SyncResolver " << url << " error:" << uv_strerror(r);
      return false;
    }
    return true;
  });
}

int PandaImpl::ResolverImpl::AsyncResolver(const char* url, char* ips[],
                                           int max_size) {
  if (this->thread_->IsCurrent()) return 0;
  std::promise<int> ret;
  if (SyncResolver(url, [&](int code, const char** tIps, int num) {
        int ips_num = std::min(max_size, num);
        for (int i = 0; i < ips_num; i++) {
          memcpy(ips[i], tIps[i], strlen(tIps[i]));
        }
        ret.set_value(ips_num);
        return;
      })) {
    return ret.get_future().get();
  }
  return 0;
}

void PandaImpl::ResolverImpl::OnResolverCb(uv_getaddrinfo_t* req, int status,
                                           addrinfo* res) {
  auto it = std::find_if(
      this->resolver_info.begin(), this->resolver_info.end(),
      [&](ResolverContext& context) { return &context.req == req; });
  if (it == this->resolver_info.end()) {
    LOG(Error) << "SyncResolver OnResolverCb Can`t Find Context";
    return;
  }
  if (status < 0) {
    LOG(Error) << "SyncResolver OnResolverCb" << it->url
               << " error:" << uv_strerror(status);
    it->on_resolver(-1, nullptr, 0);
    return;
  }

  struct addrinfo* ai;
  char addr[INET6_ADDRSTRLEN];
  for (ai = res; ai != NULL; ai = ai->ai_next) {
    if (ai->ai_family == AF_INET) {
      struct sockaddr_in* sa =
          reinterpret_cast<struct sockaddr_in*>(ai->ai_addr);
      uv_ip4_name(sa, addr, INET_ADDRSTRLEN);
      it->ip.push_back(addr);
    } else if (ai->ai_family == AF_INET6) {
      struct sockaddr_in6* sa =
          reinterpret_cast<struct sockaddr_in6*>(ai->ai_addr);
      uv_ip6_name(sa, addr, INET6_ADDRSTRLEN);
      it->ip.push_back(addr);
    }
  }
  const char** ips = new const char*[it->ip.size()];
  for (size_t i = 0; i < it->ip.size();i++) {
    ips[i] = it->ip[i].c_str();
  }
  it->on_resolver(0, ips, it->ip.size());
  this->resolver_info.erase(it);
  delete[] ips;
}
